Micron Document
πŸŽ–οΈGitΠ―Ρ€Π°πŸŽ–οΈ


Displaying Raw β€’ Download

.specify/extensions/review/.github/workflows/ci.yml 4e48e64e786b58d7c73280f7aa6ebf55be9e53e5 (4e48e64e) Text, 8.62 KB

Tff7b72nameTb4b4b4: Ta5d6ffCI

Tff7b72onTb4b4b4:
Tff7b72pushTb4b4b4:
Tff7b72branchesTb4b4b4: Tb4b4b4[Te6edf3mainTb4b4b4]
Tff7b72pull_requestTb4b4b4:
Tff7b72branchesTb4b4b4: Tb4b4b4[Te6edf3mainTb4b4b4]

Tff7b72permissionsTb4b4b4:
Tff7b72contentsTb4b4b4: Ta5d6ffread

Tff7b72jobsTb4b4b4:
Tff7b72validate-manifestTb4b4b4:
Tff7b72nameTb4b4b4: Ta5d6ffValidateTa5d6ff Ta5d6ffManifestTa5d6ff Ta5d6ff&Ta5d6ff Ta5d6ffCommands
Tff7b72runs-onTb4b4b4: Ta5d6ffubuntu-latest
Tff7b72stepsTb4b4b4:
Tb4b4b4- Tff7b72usesTb4b4b4: Ta5d6ffactions/checkout@v7.0.0

Tb4b4b4- Tff7b72nameTb4b4b4: Ta5d6ffCheckTa5d6ff Ta5d6ffrequiredTa5d6ff Ta5d6fffiles
Tff7b72runTb4b4b4: Tb4b4b4|
Tff7b72files=("README.md" "CHANGELOG.md" "extension.yml" "LICENSE")
Tff7b72for f in "${files[@]}"; do
Tff7b72if [ -f "$f" ]; then
Tff7b72echo "βœ… $f exists"
Tff7b72else
Tff7b72echo "❌ $f missing"
Tff7b72exit 1
Tff7b72fi
Tff7b72done

Tb4b4b4- Tff7b72nameTb4b4b4: Ta5d6ffValidateTa5d6ff Ta5d6ffextension.yml
Tff7b72runTb4b4b4: Tb4b4b4|
Tff7b72# Validate YAML syntax
Tff7b72python3 -c "import yaml; yaml.safe_load(open('extension.yml'))" || {
Tff7b72echo "❌ extension.yml has invalid YAML syntax"
Tff7b72exit 1
Tff7b72}
Tff7b72echo "βœ… extension.yml has valid YAML syntax"

Tff7b72# Deep manifest validation
Tff7b72python3 << 'PYEOF'
Tff7b72import yaml, sys, re, os

Tff7b72with open('extension.yml') as f:
Tff7b72ext = yaml.safe_load(f)

Tff7b72errors = []

Tff7b72# --- Top-level required fields ---
Tff7b72for field in ['schema_version', 'extension']:
Tff7b72if field not in ext:
Tff7b72errors.append(f"Missing required field: {field}")

Tff7b72if 'extension' not in ext:
Tff7b72for e in errors:
Tff7b72print(f"❌ {e}")
Tff7b72sys.exit(1)

Tff7b72meta = ext['extension']

Tff7b72for field in ['id', 'name', 'version', 'description', 'author']:
Tff7b72if field not in meta:
Tff7b72errors.append(f"Missing extension.{field}")

Tff7b72# --- Extension ID format (lowercase, alphanumeric, hyphens) ---
Tff7b72ext_id = meta.get('id', '')
Tff7b72if ext_id and not re.match(r'^[a-z0-9-]+$', ext_id):
Tff7b72errors.append(f"Extension ID '{ext_id}' must match ^[a-z0-9-]+$ (lowercase, alphanumeric, hyphens only)")
Tff7b72if ext_id:
Tff7b72print(f"βœ… Extension ID '{ext_id}' has valid format")

Tff7b72# --- SemVer validation ---
Tff7b72version = meta.get('version', '')
Tff7b72if version and not re.match(r'^\d+\.\d+\.\d+$', version):
Tff7b72errors.append(f"Version '{version}' must follow semantic versioning (X.Y.Z)")
Tff7b72if version:
Tff7b72print(f"βœ… Version '{version}' is valid SemVer")

Tff7b72# --- Description length (under 200 chars) ---
Tff7b72desc = meta.get('description', '')
Tff7b72if len(desc) > 200:
Tff7b72errors.append(f"Description is {len(desc)} characters (must be under 200)")
Tff7b72if desc:
Tff7b72print(f"βœ… Description is {len(desc)} characters (under 200 limit)")

Tff7b72# --- requires.speckit_version ---
Tff7b72requires = ext.get('requires', {})
Tff7b72if not requires or 'speckit_version' not in requires:
Tff7b72errors.append("Missing requires.speckit_version (required)")
Tff7b72else:
Tff7b72print(f"βœ… requires.speckit_version: {requires['speckit_version']}")

Tff7b72# --- provides.commands exists and is non-empty ---
Tff7b72provides = ext.get('provides', {})
Tff7b72commands = provides.get('commands', [])
Tff7b72if not commands:
Tff7b72errors.append("provides.commands must exist and contain at least one command")
Tff7b72else:
Tff7b72print(f"βœ… provides.commands has {len(commands)} command(s)")

Tff7b72cmd_pattern = f'^speckit\\.{re.escape(ext_id)}\\.[a-z0-9-]+$'

Tff7b72# --- Command naming convention: speckit.{ext-id}.{command} ---
Tff7b72for cmd in commands:
Tff7b72name = cmd.get('name', '')
Tff7b72if not re.match(cmd_pattern, name):
Tff7b72errors.append(f"Command name '{name}' must match pattern speckit.{ext_id}.<command>")
Tff7b72else:
Tff7b72print(f"βœ… Command '{name}' follows naming convention")

Tff7b72# --- Alias naming convention: speckit.{ext-id}.{alias} ---
Tff7b72for cmd in commands:
Tff7b72aliases = cmd.get('aliases', [])
Tff7b72if aliases is None:
Tff7b72aliases = []
Tff7b72if not isinstance(aliases, list):
Tff7b72errors.append(f"Aliases for command '{cmd.get('name', '')}' must be a list")
Tff7b72continue
Tff7b72for alias in aliases:
Tff7b72if not isinstance(alias, str):
Tff7b72errors.append(f"Alias in command '{cmd.get('name', '')}' must be a string")
Tff7b72continue
Tff7b72if not re.match(cmd_pattern, alias):
Tff7b72errors.append(f"Alias '{alias}' must match pattern speckit.{ext_id}.<command>")
Tff7b72else:
Tff7b72print(f"βœ… Alias '{alias}' follows naming convention")

Tff7b72# --- Command file existence ---
Tff7b72for cmd in commands:
Tff7b72cmd_file = cmd.get('file', '')
Tff7b72if cmd_file and not os.path.isfile(cmd_file):
Tff7b72errors.append(f"Command file '{cmd_file}' referenced in manifest does not exist")
Tff7b72elif cmd_file:
Tff7b72print(f"βœ… Command file '{cmd_file}' exists")

Tff7b72# --- Config template existence ---
Tff7b72configs = provides.get('config', [])
Tff7b72for cfg in configs:
Tff7b72template = cfg.get('template', '')
Tff7b72if template and not os.path.isfile(template):
Tff7b72errors.append(f"Config template '{template}' referenced in manifest does not exist")
Tff7b72elif template:
Tff7b72print(f"βœ… Config template '{template}' exists")

Tff7b72# --- Report ---
Tff7b72if errors:
Tff7b72print()
Tff7b72for e in errors:
Tff7b72print(f"❌ {e}")
Tff7b72sys.exit(1)

Tff7b72print()
Tff7b72print(f"βœ… All manifest validations passed")
Tff7b72print(f" Extension: {meta['name']} v{meta['version']}")
Tff7b72PYEOF

Tb4b4b4- Tff7b72nameTb4b4b4: Ta5d6ffValidateTa5d6ff Ta5d6ffcommandTa5d6ff Ta5d6fffiles
Tff7b72runTb4b4b4: Tb4b4b4|
Tff7b72if [ -d commands ]; then
Tff7b72for cmd in commands/*.md; do
Tff7b72if [ -f "$cmd" ]; then
Tff7b72# Check frontmatter exists
Tff7b72if head -1 "$cmd" | grep -q "^---"; then
Tff7b72echo "βœ… $cmd has frontmatter"

Tff7b72# Check description field in frontmatter
Tff7b72if python3 -c "
Tff7b72import yaml, sys
Tff7b72with open('$cmd') as f:
Tff7b72content = f.read()
Tff7b72parts = content.split('---', 2)
Tff7b72if len(parts) >= 3:
Tff7b72fm = yaml.safe_load(parts[1])
Tff7b72if fm and 'description' in fm:
Tff7b72sys.exit(0)
Tff7b72sys.exit(1)
Tff7b72" 2>/dev/null; then
Tff7b72echo "βœ… $cmd has 'description' in frontmatter"
Tff7b72else
Tff7b72echo "❌ $cmd missing 'description' in frontmatter (required)"
Tff7b72exit 1
Tff7b72fi
Tff7b72else
Tff7b72echo "❌ $cmd missing frontmatter"
Tff7b72exit 1
Tff7b72fi
Tff7b72fi
Tff7b72done
Tff7b72else
Tff7b72echo "❌ No commands directory found"
Tff7b72exit 1
Tff7b72fi

Tff7b72validate-bashTb4b4b4:
Tff7b72nameTb4b4b4: Ta5d6ffValidateTa5d6ff Ta5d6ff&Ta5d6ff Ta5d6ffTestTa5d6ff Ta5d6ffBashTa5d6ff Ta5d6ffScriptsTa5d6ff Ta5d6ff(${{Ta5d6ff Ta5d6ffmatrix.osTa5d6ff Ta5d6ff}})
Tff7b72runs-onTb4b4b4: Ta5d6ff${{Ta5d6ff Ta5d6ffmatrix.osTa5d6ff Ta5d6ff}}
Tff7b72strategyTb4b4b4:
Tff7b72matrixTb4b4b4:
Tff7b72osTb4b4b4: Tb4b4b4[Te6edf3ubuntu-latestTb4b4b4, Te6edf3macos-latestTb4b4b4]
Tff7b72stepsTb4b4b4:
Tb4b4b4- Tff7b72usesTb4b4b4: Ta5d6ffactions/checkout@v7.0.0
Tff7b72withTb4b4b4:
Tff7b72submodulesTb4b4b4: Ta5d6ffrecursive

Tb4b4b4- Tff7b72nameTb4b4b4: Ta5d6ffCheckTa5d6ff Ta5d6ffBashTa5d6ff Ta5d6ffscriptsTa5d6ff Ta5d6ffsyntax
Tff7b72runTb4b4b4: Tb4b4b4|
Tff7b72echo "Bash version: $(bash --version | head -1)"
Tff7b72for script in scripts/bash/*.sh; do
Tff7b72echo "Checking $script..."
Tff7b72bash -n "$script"
Tff7b72echo " βœ“ Syntax OK"
Tff7b72done

Tb4b4b4- Tff7b72nameTb4b4b4: Ta5d6ffRunTa5d6ff Ta5d6ffBATSTa5d6ff Ta5d6fftests
Tff7b72runTb4b4b4: Tb4b4b4|
Tff7b72echo "Running BATS tests on ${{ matrix.os }}..."
Tff7b72tests/bats/lib/bats-core/bin/bats tests/bats/*.bats

Tff7b72validate-powershellTb4b4b4:
Tff7b72nameTb4b4b4: Ta5d6ffValidateTa5d6ff Ta5d6ff&Ta5d6ff Ta5d6ffTestTa5d6ff Ta5d6ffPowerShellTa5d6ff Ta5d6ffScripts
Tff7b72runs-onTb4b4b4: Ta5d6ffwindows-latest
Tff7b72stepsTb4b4b4:
Tb4b4b4- Tff7b72usesTb4b4b4: Ta5d6ffactions/checkout@v7.0.0

Tb4b4b4- Tff7b72nameTb4b4b4: Ta5d6ffCheckTa5d6ff Ta5d6ffPowerShellTa5d6ff Ta5d6ffscriptsTa5d6ff Ta5d6ffsyntax
Tff7b72shellTb4b4b4: Ta5d6ffpwsh
Tff7b72runTb4b4b4: Tb4b4b4|
Tff7b72foreach ($script in Get-ChildItem scripts/powershell/*.ps1) {
Tff7b72Write-Host "Checking $($script.Name)..."
Tff7b72$null = [System.Management.Automation.PSParser]::Tokenize(
Tff7b72(Get-Content $script.FullName -Raw), [ref]$null
Tff7b72)
Tff7b72Write-Host " OK"
Tff7b72}

Tb4b4b4- Tff7b72nameTb4b4b4: Ta5d6ffRunTa5d6ff Ta5d6ffPesterTa5d6ff Ta5d6fftests
Tff7b72shellTb4b4b4: Ta5d6ffpwsh
Tff7b72runTb4b4b4: Tb4b4b4|
Tff7b72$config = New-PesterConfiguration
Tff7b72$config.Run.Path = "tests/pester"
Tff7b72$config.Output.Verbosity = "Detailed"
Tff7b72$config.Run.Exit = $true
Tff7b72Invoke-Pester -Configuration $config

Served by rngit 1.5.2 - Generated in 0.04s